package com.cybersapien.comng.engine.event.engine;

import java.util.HashMap;
import java.util.Map;

import com.cybersapien.comng.engine.event.IEvent;
import com.cybersapien.comng.engine.event.IEvent.SCOPE;
import com.cybersapien.comng.engine.event.IEvent.TYPE;
import com.cybersapien.comng.engine.event.IEventTarget;
import com.cybersapien.comng.engine.type.IStack;
import com.cybersapien.comng.engine.type.ThreadStack;

/**
 * THREAD SAFE!
 * 
 * @author RagTime
 * 
 */
public class EventListenerManager {

	private static class RepoByListenerID extends ThreadStack<IEventEngineListerner> {
		private static final long serialVersionUID = 309544087676625590L;

		Map<String, Integer> listenersIndex = new HashMap<String, Integer>();

		/*
		 * (non-Javadoc)
		 * 
		 * @see com.cybersapien.comng.engine.utils.IStack#push(TYPE)
		 */
		@Override
		public IEventEngineListerner push(IEventEngineListerner value) {

			String id = value.getID();

			// Do not push an already present listener.
			if (!listenersIndex.containsKey(id)) {
				super.push(value);
				int index = super.count() - 1;
				listenersIndex.put(id, Integer.valueOf(index));
			}

			return value;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see com.cybersapien.comng.engine.utils.IStack#pop(TYPE)
		 */
		@Override
		public IEventEngineListerner pop() {
			IEventEngineListerner value = super.pop();
			String id = value.getID();
			listenersIndex.put(id, null);
			return value;
		}
	};

	private static class RepoByTargetID extends HashMap<String, RepoByListenerID> {
		private static final long serialVersionUID = 309544087676625590L;
	};

	private static class RepoByType extends HashMap<IEvent.TYPE, RepoByTargetID> {
		private static final long serialVersionUID = 6033554788602902915L;
	};

	private static class RepoByScope extends HashMap<IEvent.SCOPE, RepoByType> {
		private static final long serialVersionUID = -500028887490218837L;
	};

	private static class RepoByWorld extends HashMap<String, RepoByScope> {
		private static final long serialVersionUID = -2752109021808710865L;
	};

	/* ====== SINGLETON MANAGEMENT ===== */

	static private EventListenerManager instance = null;

	static public EventListenerManager getInstance() {
		if (instance == null) {
			initInstance();
		}
		return instance;
	}

	static private synchronized void initInstance() {
		if (instance == null) {
			instance = new EventListenerManager();
		}
	}

	/* ===== */

	/**
	 * 
	 */
	RepoByWorld listenersRepository;

	/**
	 * 
	 */
	private EventListenerManager() {
		listenersRepository = new RepoByWorld();
	}

	/**
	 * 
	 * @param scope
	 * @param target
	 * @param listener
	 * @return
	 */
	public IEventEngineListerner addListener(String worldID, SCOPE scope, TYPE type, IEventTarget target, IEventEngineListerner listener) {
		RepoByListenerID listeners = getListernersForWorldAndScopeAndTarget(worldID, scope, type, target);
		listeners.push(listener);
		return listener;
	}

	/**
	 * 
	 * @param scope
	 * @param target
	 * @return
	 */
	public IStack<IEventEngineListerner> findListeners(IEvent event) {
		String worldID = event.getWorldID();
		SCOPE scope = event.getScope();
		TYPE type = event.getType();
		IEventTarget target = event.getTarget();
		RepoByListenerID listenersRepo = getListernersForWorldAndScopeAndTarget(worldID, scope, type, target);

		return listenersRepo;
	}

	/**
	 * 
	 * @param scope
	 * @return
	 */
	protected RepoByScope getListernersForWorld(String worldID) {
		RepoByScope listeners = listenersRepository.get(worldID);
		if (listeners == null) {
			synchronized (this) {
				listeners = listenersRepository.get(worldID);
				if (listeners == null) {
					listeners = new RepoByScope();
					listenersRepository.put(worldID, listeners);
				}
			}
		}
		return listeners;
	}

	/**
	 * 
	 * @param scope
	 * @param target
	 * @return
	 */
	protected RepoByType getListernersForWorldAndScope(String worldID, SCOPE scope) {
		RepoByScope listenersForScope = getListernersForWorld(worldID);
		RepoByType listeners = listenersForScope.get(scope);

		if (listeners == null) {
			synchronized (this) {
				listeners = listenersForScope.get(scope);
				if (listeners == null) {
					listeners = new RepoByType();
					listenersForScope.put(scope, listeners);
				}
			}
		}

		return listeners;
	}

	/**
	 * 
	 * @param scope
	 * @param target
	 * @return
	 */
	protected RepoByTargetID getListernersForWorldAndScopeAndType(String worldID, SCOPE scope, TYPE type) {
		RepoByType listenersForScope = getListernersForWorldAndScope(worldID, scope);
		RepoByTargetID listeners = listenersForScope.get(type);

		if (listeners == null) {
			synchronized (this) {
				listeners = listenersForScope.get(type);
				if (listeners == null) {
					listeners = new RepoByTargetID();
					listenersForScope.put(type, listeners);
				}
			}
		}

		return listeners;
	}

	/**
	 * 
	 * @param scope
	 * @param target
	 * @return
	 */
	protected RepoByListenerID getListernersForWorldAndScopeAndTarget(String worldID, SCOPE scope, TYPE type, IEventTarget target) {
		RepoByTargetID listenersByID = getListernersForWorldAndScopeAndType(worldID, scope, type);

		RepoByListenerID listeners = listenersByID.get(target.getID());
		if (listeners == null) {
			synchronized (this) {
				listeners = listenersByID.get(target.getID());
				if (listeners == null) {
					listeners = new RepoByListenerID();
					listenersByID.put(target.getID(), listeners);
				}
			}
		}
		return listeners;
	}

}
